package binding

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

type (
	Binding interface {
		// Bind 统一的Bind方法用于处理请求参数,处理失败返回一个错误
		Bind(*http.Request, any) error
	}

	jsonBinding struct{}

	xmlBinding struct{}

	formBinding struct{}
)

// 初始化
var (
	JSON = jsonBinding{}
	XML  = xmlBinding{}
	Form = formBinding{} // todo
)

// Bind 尝试将消息正文数据按json格式处理,并对其中字段进行校验
func (_ jsonBinding) Bind(req *http.Request, obj any) error {
	decoder := json.NewDecoder(req.Body)
	if err := decoder.Decode(obj); err == nil {
		return Validate(obj)
	} else {
		return err
	}
}

// Bind 处理xml类型
func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error {
	decoder := xml.NewDecoder(req.Body)
	if err := decoder.Decode(obj); err == nil {
		return Validate(obj)
	} else {
		return err
	}
}

// Bind 处理表单数据,obj为要映射的类型
func (_ formBinding) Bind(req *http.Request, obj interface{}) error {
	// req.ParseForm() 用于解析http请求中的表单数据
	// 解析后的数据会存储在req.Form和req.PostForm这两个字段中
	if err := req.ParseForm(); err != nil {
		return err
	}

	// req.Form 大概长这样:map[email:[123] username:[123123]]
	if err := mapForm(obj, req.Form); err != nil {
		return err
	}
	return Validate(obj)
}

// mapForm 将表单数据通过反射映射到结构体中
func mapForm(ptr any, form map[string][]string) error {
	// 去指针类型对应类型和值(如果是指针类型的话)
	typ := reflect.TypeOf(ptr).Elem()
	formStruc := reflect.ValueOf(ptr).Elem()

	// 遍历所有字段
	for i := 0; i < typ.NumField(); i++ {
		typeField := typ.Field(i)
		// 尝试获取标签中的form值,如果获取到了那么则帮其设置值
		if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" {
			structField := formStruc.Field(i)
			// CanSet 用于判断该字段是否可设置,需满足如下两条：
			// 1.可寻址,该值是一个变量或者指向变量的指针(如果是常量或函数返回的临时值则不可设置)
			// 2.不能是未导出的字段(字段以小写字母开头的)
			// 如果当前值不可设置则跳过
			if !structField.CanSet() {
				continue
			}
			// 尝试获取表单值
			inputValue, exists := form[inputFieldName]
			if !exists {
				continue
			}
			numElems := len(inputValue)
			// 判断当前是否为切片类型,且有值
			if structField.Kind() == reflect.Slice && numElems > 0 {
				// 返回切片类型
				sliceOf := structField.Type().Elem().Kind()
				slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
				// 遍历切片,根据类型分别设置值
				for i := 0; i < numElems; i++ {
					if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
						return err
					}
				}
				formStruc.Elem().Field(i).Set(slice)
			} else {
				// 如果不是切片类型,直接设置即可
				if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// setWithProperType 设置属性值
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
	switch valueKind {
	// 如果是整型
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
		if val == "" {
			val = "0"
		}
		// 字符串转int
		intVal, err := strconv.Atoi(val)
		if err != nil {
			return nil
		} else {
			// 给结构体字段赋值
			structField.SetInt(int64(intVal))
		}
	case reflect.Bool:
		if val == "" {
			val = "false"
		}
		boolVal, err := strconv.ParseBool(val)
		if err != nil {
			return err
		} else {
			structField.SetBool(boolVal)
		}
	case reflect.Float32:
		if val == "" {
			val = "0.0"
		}
		floatVal, err := strconv.ParseFloat(val, 32)
		if err != nil {
			return err
		} else {
			structField.SetFloat(floatVal)
		}
	case reflect.Float64:
		if val == "" {
			val = "0.0"
		}
		floatVal, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return err
		} else {
			structField.SetFloat(floatVal)
		}
	case reflect.String:
		structField.SetString(val)
	}
	return nil
}

func Validate(obj any) error {
	// 获取传入对象的类型和值
	typ := reflect.TypeOf(obj)
	val := reflect.ValueOf(obj)

	// 检查是否为指针类型,是则返回指针所指向的类型和值
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		val = val.Elem()
	}

	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)
		// 这里转interface{}是为了后续递归和判断用
		fieldValue := val.Field(i).Interface()

		// 创建类型零值,用于比较结构体字段值是否为零值
		zero := reflect.Zero(field.Type).Interface()

		// 如果是结构体或者是指针类型,并且不是零值的情况下则递归调用判断)
		if field.Type.Kind() == reflect.Struct || (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) {
			// 满足条件,递归调用(主要防止结构体嵌套)
			if err := Validate(fieldValue); err != nil {
				return err
			}
		}
		// 看tag中是否由required,如果有则需要验证该值是否为零值
		// 在binding串里查找子串required的索引,不存在则返回-1
		if strings.Index(field.Tag.Get("binding"), "required") > -1 {
			// 判断字段值是否为零值
			if reflect.DeepEqual(zero, fieldValue) {
				// 如果字段值是零值且存在required标签,则表示该字段是必须字段但未提供值
				name := field.Name
				// 检查字段标签是否存在json和form,存在则使用其值作为字段名称
				// 以此错误错误信息,更为直观
				if j := field.Tag.Get("json"); j != "" {
					name = j
				} else if f := field.Tag.Get("form"); f != "" {
					name = f
				}
				return errors.New("Require " + name)
			}
		}

	}
	return nil
}
